<?php

namespace App\Library;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Ip2Region;

class ClientIp
{
    private string $ip;

    /**
     * @param string $ip
     * @return ClientIp
     */
    public static function init(string $ip = ''): static
    {
        $clientIp = new static;
        $clientIp->ip = $ip ?: $clientIp->getClientIp();
        return $clientIp;
    }

    /**
     * @return string
     */
    private function getClientIp(): string
    {
        $ipStr = '0.0.0.0';
        if (getenv('HTTP_CLIENT_IP')) {
            $ipStr = getenv('HTTP_CLIENT_IP');
        } elseif (getenv('HTTP_X_REAL_IP')) {
            $ipStr = getenv('HTTP_X_REAL_IP');
        } elseif (getenv('HTTP_X_FORWARDED_FOR')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
            $ips = explode(',', $ip);
            $ipStr = $ips[0];
        } elseif (getenv('REMOTE_ADDR')) {
            $ipStr = getenv('REMOTE_ADDR');
        }
        return $ipStr;
    }

    /**
     * @return string
     */
    public function getIp(): string
    {
        return $this->ip;
    }

    /**
     * @param string $ip
     * @return ClientIp
     */
    public function setIp(string $ip): static
    {
        $this->ip = $ip;
        return $this;
    }

    /**
     * @return array
     */
    public function get(): array
    {
        $method = config('custom.ipMethod');
        return match ($method) {
            'pconline' => $this->pcOnline(),
            'ipapi' => $this->ipApi(),
            default => $this->ip2region(),
        };
    }

    /**
     * @return array
     */
    private function ip2region(): array
    {
        $defData = $this->toData();
        try {
            $info = (new Ip2Region())->btreeSearch($this->ip);
            [$country, $area, $province, $city] = explode("|", $info["region"]);
            return array_merge($defData, [
                'country' => $country,
                'province' => $province,
                'city' => $city,
                'area' => $area,
                'region' => $province.' '.$city,
            ]);
        }catch (\Exception $e) {
            report($e);
        }
        return $defData;
    }

    /**
     * @return array
     */
    private function pcOnline(): array
    {
        $defData = $this->toData();
        try {
            $resp = $this->initClient()->get('https://whois.pconline.com.cn/ipJson.jsp', [
                'query' => [
                    'ip' => $this->ip,
                    'json' => true,
                ],
                'headers' => [
                    'Content-Type' => 'application/json;charset=UTF-8',
                ]
            ]);
            $data = json_decode($resp->getBody()->getContents(), true);
            return array_merge($defData, [
                'province' => $data["pro"],
                'province_code' => $data["proCode"],
                'city' => $data["city"],
                'city_code' => $data["cityCode"],
                'area' => $data["region"],
                'area_code' => $data["regionCode"],
                'region' => $data["addr"],
            ]);
        }catch (GuzzleException $e) {
            report($e);
        }
        return $defData;
    }

    /**
     * @return array
     */
    private function ipApi(): array
    {
        $defData = $this->toData();
        try {
            $resp = $this->initClient()->get('https://ip-api.com/php/'.$this->ip, [
                'query' => [
                    'fields' => 61439,
                    'lang' => 'zh-CN'
                ],
                'headers' => [
                    'Content-Type' => 'application/json;charset=UTF-8',
                ]
            ]);
            $data = json_decode($resp->getBody()->getContents(), true);
            return array_merge($defData, [
                'country' => $data['country'],
                'province' => $data["regionName"],
                'city' => $data["city"],
                'lat' => $data['lat'],
                'lon' => $data['lon'],
                'region' => $data["regionName"].' '.$data["city"],
            ]);
        }catch (GuzzleException $e) {
            report($e);
        }
        return $defData;
    }

    /**
     * @return array
     */
    private function toData(): array
    {
        return [
            'ip' => $this->ip,
            'country' => '',
            'province' => '',
            'province_code' => '',
            'city' => '',
            'city_code' => '',
            'area' => '',
            'area_code' => '',
            'region' => '',
            'lat' => 0,
            'lon' => 0
        ];
    }

    /**
     * @return Client
     */
    private function initClient(): Client
    {
        return new Client([
            'timeout' => 5.0,
            'verify' => false,
        ]);
    }
}
